/* 
JS中数据类型检测汇总
  @1 typeof [value]
    返回值：首先是一个字符串，其次字符串中包含了对应的数据类型「“number”、“object”、“function”...」
    特点：
      + typeof null -> "object" 
      + 不能细分对象：基于 typeof 检测对象的时候，除了函数会返回 “function”，其余的都返回 “object”
      + 基于 typeof 检测一个未被声明的变量，不会报错，结果是 “undefined”
    原理：
      typeof 在检测数据类型的时候，是按照数据值在计算机底层存储的 二进制 值，进行检测的！
      其中，如果二进制值的前三位是零，都被识别为对象，然后再看此对象是否具备call方法，具备call方法则返回“function”「说明其是一个函数」，不具备的返回“object”「说明其是其他对象」！
      而null在计算机底层的二进制值都是零，也就是前三位也是零，所以 typeof null 的结果是 "object" ！
    应用：
      + 检测除null之外的原始值类型「typeof用起来方便，而且性能还好」
      + 判断兼容性
      + 笼统检测是否为对象
      + ...

  @2 对象 instanceof 构造函数
    原本的意义是用来检测“某个对象是否是相应类的实例”，只不过针对于这个特点，我们可以用其检测一些数据类型
      + 检测是否为数组：值 instanceof Array
      + 检测是否为正则：值 instanceof RegExp
      + ...
      也就是基于 instanceof ，可以弥补 typeof 不能细分对象的缺陷！
    特点：
      + 无法检测原始值类型，返回结果都是false {原因：基于instanceof进行操作，不会对原始值类型进行“装箱”}
        10 instanceof Number -> false
      + 原本不是检测数据类型的，现在非要让其检测类型，所以检测的结果不一定精准 
    原理：
      首先看 构造函数Ctor 是否具备 Symbol.hasInstance 这个属性
        + 具备：Ctor[Symbol.hasInstance](obj)
        + 不具备：依次查找对象的原型链（__proto__），一直到 Object.prototype ，在此过程中，如果 构造函数.prototype 出现在了其原型链的某个环节，则说明 当前对象 是此构造函数的一个实例，检测结果就是true！
      在支持ES6的浏览器中，Function.prototype 上具备 Symbol.hasInstance 方法，所以只要是函数，也都具备这个方法！！
        arr instanceof Array --> Array[Symbol.hasInstance](arr)

  @3 constructor
    获取对象的构造函数，从而判断是否是属于某一个数据类型
    只不过这种方式我们一般很少去使用，因为 constructor 值是可以被更改的「修改值的成本低」，一但被更改，则检测结果是不准确的！

  @4 Object.prototype.toString.call([value])
    + 不仅仅 Object.prototype 上有 toString 方法，在 Number/String/Boolen/Array/Function... 的原型对象上，也有 toString 方法，只不过其它原型上的toString方法都是用来转换为字符串的，只有Object.prototype.toString是用来检测数据类型的
    + 把 Object.prototype 上的 toString 方法执行，让方法中的 this 指向要检测的数据值，这样就可以返回此数据值的数据类型 -> "[object ?]"
      特点：精准且强大「唯一不足就是写起来麻烦一丢丢」
      + “？”一般情况下，就是检测值所属的构造函数（前提：内置的构造函数）
        const toString = Object.prototype.toString
        toString.call(null) -> “[object Null]”  虽然Null不是构造函数，但是结果还是很准的
        toString.call(undefined) -> “[object Undefined]”
        toString.call([]) -> “[object Array]”
        toString.call(/\d+/) -> “[object RegExp]”
        ...
      + 如果被检测的值具备 Symbol.toStringTag 这个属性，那么属性值是啥，最后检测结果中的“？”就是啥
        在Promise.prototype上，具备 Symbol.toStringTag 这个属性，属性值是 "Promise"
        let p = new Promise(()=>{})
        toString.call(p) -> "[object Promise]"
    此办法虽然很不错，但是也不是所有的数据类型检测都使用这个办法，一般来讲：需要笼统的检测或者按照大的类别去检测，使用 typeof 会更方便，而需要很精准检测的时候，使用 toString 会更好！！

  ---
  快捷方法：
    + isNaN 检测是否为有效数字
    + Array.isArray 检测是否为数组
    + ...
*/
const toString = Object.prototype.toString

/*
function Fn() { }
Fn.prototype[Symbol.toStringTag] = 'Fn'
let f = new Fn
console.log(toString.call(f)) //"[object Fn]"
*/

/*
function Fn() { }
let f = new Fn
console.log(toString.call(f)) //"[object Object]"
*/

/* let arr = [10, 20],
    obj = { x: 10, y: 20 }
console.log(toString.call(arr)) //"[object Array]"
console.log(toString.call(obj)) //"[object Object]" */

/* let arr = [10, 20]
console.log(arr.toString()) //"10,20"  使用的是Array.prototype.toString
let obj = { x: 10, y: 20 }
console.log(obj.toString()) //"[object Object]" 使用的是Object.prototype.toString */


//----------------------------------------------
/* let arr = [10, 20]
console.log(arr.constructor === Array) //true
console.log(arr.constructor === Object) //false  如果一个对象的constructor等于Object，说明该对象的__proto__直接指向Object.prototype，也就是说明此对象是“标准普通对象”
console.log(arr instanceof Array) //true
console.log(arr instanceof Object) //true

let num = 10
console.log(num.constructor === Number) //true  支持原始值类型的检测，因为访问 constructor 属性的时候，原始值会默认的进行“装箱” */


//----------------------------------------------
/*
 _instanceof：对内置 instanceof 运算符的重写
   @params
     obj：要检测的对象
     Ctor：要被检测的构造函数
   @return
     布尔值
 */
/* const _instanceof = function _instanceof(obj, Ctor) {
    // 必须保证Ctor得是一个函数
    if (typeof Ctor !== "function") throw new TypeError(`Right-hand side of 'instanceof' is not callable`)
    // instanceof无法检测原始值类型
    if (obj == null || !/^(object|function)$/.test(typeof obj)) return false
    // Ctor这个函数必须具备prototype
    if (!Ctor.prototype) throw new TypeError(`Function has non-object prototype 'undefined' in instanceof check`)

    if (Ctor[Symbol.hasInstance]) {
        // 具备 Symbol.hasInstance 的构造函数，则直接基于这个方法执行去进行校验即可
        return Ctor[Symbol.hasInstance](obj)
    }

    let proto = Object.getPrototypeOf(obj) //获取对象的原型链，类似于 obj.__proto__
    // 一直找，直到找到尽头为止
    while (proto) {
        if (proto === Ctor.prototype) return true //在查找中，如果出现了Ctor.prototype，说明对象是此构造函数的一个实例，返回true即可
        proto = Object.getPrototypeOf(proto) // 获取其原型对象的原型链
    }
    return false //都找完也没有符合条件的，说明对象不是此构造函数的一个实例，直接返回false
} */
/* let arr = [10, 20]
console.log(_instanceof(arr, Array)) //true
console.log(_instanceof(arr, Object)) //true
console.log(_instanceof(arr, RegExp)) //false */

/* const Fn = function Fn() {
    this.x = 10
}
Fn.prototype = new Array()
Fn.prototype.name = 'Fn'
let f = new Fn
// f.__proto__ -> Fn.prototype -> Array.prototype -> Object.prototype
console.log(f instanceof Fn) //true
console.log(f instanceof Object) //true
console.log(f instanceof Array) //true
console.log(f instanceof RegExp) //false
console.log(f) */

/*
// 正常情况下，直接这样重写 Symbol.hasInstance 是不会生效的
Array[Symbol.hasInstance] = function (obj) {
    console.log('AAA')
}
const Fn = function Fn() { }
Fn.prototype = new Array()
Fn.prototype.constructor = Fn
let f = new Fn
console.log(f)
console.log(f instanceof Array) //true  等价于 Array[Symbol.hasInstance](f)
*/

/*
class Fn {
    constructor(name) {
        if (name) this.name = name
    }
    // 设置其静态私有属性方法「这样重写会生效」
    static [Symbol.hasInstance](obj) {
        return obj.hasOwnProperty('name')
    }
}
let f1 = new Fn
let f2 = new Fn('珠峰')
console.log(f1 instanceof Fn) //false   Fn[Symbol.hasInstance](f1)
console.log(f2 instanceof Fn) //true   Fn[Symbol.hasInstance](f2)
*/


//------------------------
// console.log(typeof typeof typeof [10, 20]) //"string"

/* 
let num = 10 //原始值类型
console.log(num.toFixed(2)) //"10.00"  默认进行了“装箱”操作（把原始值转换为对象）num->Object(num)
let num2 = new Number(10) //对象类型
console.log(num2 + 10) //20  默认进行了“拆箱”操作（把对象转换为原始值）num2[Symbol.toPrimitive] -> num2['valueOf']() 
*/
